package de.akabahn.ebd.stw.zdxfc.prs.elemente;

import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.BIT0;
import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.SIGNAL;

import org.apache.log4j.Logger;

import de.akabahn.ebd.dbd.impl.DBDSignalF;
import de.akabahn.ebd.stw.zdxfc.prs.ZDXFCMain;

/**
 * Implementierung des Fahrwegelements <code>Signal</code>.<br>
 * <br>
 * Bitcodierung des Signal-Haltmelders (signalMelder[0])<br>
 * <pre>
 * BIT1 BIT0  BEDEUTUNG
 *    0    0  AUS
 *    0    1  ROT AN
 *    1    0  ROT BLINKT
 * </pre>
 * <br>
 * Bitcodierung des Signal-Verschlussmelders (signalMelder[1])<br>
 * <pre>
 * BIT1 BIT0  BEDEUTUNG
 *    0    0  AUS
 *    0    1  WEISS AN
 *    1    0  WEISS BLINKT
 * </pre>
 *   
 * @author Peter Eimann
 * @since 1.0
 * @version 1.2
 */
public class Signal extends FahrwegElement {

	private final static Logger LOG = Logger.getLogger(Signal.class);
	
	private int signalBegriff;
	private int haltBegriff;
	private String[] signalMelder;
	private boolean haltfallManuell;
	private String fahrStrassenId;
	private Signal vorsignal;
	
	/**
	 * Initialisierung der Klasse
	 * 
	 * @param id
	 *            Eindeutige ID des Elements, wird fuer die Referenz auf das
	 *            Element benoetigt (die ID ist der Schluessel in den Maps, in
	 *            denen das Element gespeichert ist.
	 * @param signalMelder
	 *            Name der DBD-Variablen fuer den dem Signal zugeordneten Halt-
	 *            (Position 0) und Verschlussmelder (Position 1) im TCO
	 * @param haltBegriff
	 *            Signalbegriff der Haltstellung des Signals
	 * @param main
	 *            Referenz auf die Hauptklasse
	 */
	public Signal(String id, String[] signalMelder, int haltBegriff, ZDXFCMain main) {
		super(id, main);
		this.signalMelder = signalMelder;
		this.haltBegriff = haltBegriff;
	}

	/**
	 * Initialisierung des Signals in Haltstellung (zusammen mit dem HAGT-Flag)
	 * 
	 * @see #stellen(int)
	 */
	public void init() {
		LOG.debug(id + ": Init");
		stellen(haltBegriff + DBDSignalF.HAGT);
	}
	
	/**
	 * Gibt des aktuellen Signalbegriff des Signals zurueck (fuer die Kodierung siehe die Klasse
	 * <code>de.akabahn.ebd.dbd.impl.DBDSignalF</code> im <code>EBD_DBD</code> Projekt.
	 * 
	 * @return Der Signalbegriff
	 */
	public int getSignalBegriff() {
		return signalBegriff;
	}

	/**
	 * Setzt den aktuellen Signalbegriff des Signals (diese Methode schreibt den Begriff nicht in die entsprechende
	 * DBD-Variable, dafuer muss <code>stellen(int)</code> aufgerufen werden).
	 * 
	 * @param signalBegriff Der neue aktuelle Signalbegriff
	 * @see #stellen(int)
	 */
	public void setSignalBegriff(int signalBegriff) {
		this.signalBegriff = signalBegriff;
		LOG.info(id + ": Signalbegriff = " + new DBDSignalF(signalBegriff).toString());
	}

	/**
	 * Gibt die ID der Fahrstrasse zurueck, fuer die das Signal gestellt und das Startsignal ist
	 * 
	 * @return Die ID der einstellten Fahrstrasse (<code>null</code> wenn Signal aktuell kein Startsignal ist)
	 */
	public String getFahrStrassenId() {
		return fahrStrassenId;
	}

	/**
	 * Setzt die ID der Fahrstrasse, fuer die das Signal gestellt und Startsignal ist
	 * 
	 * @param fahrStrassenId Die ID der eingestellten Fahrstrasse (<code>null</code> um die ID zu loeschen)
	 */
	public void setFahrStrassenId(String fahrStrassenId) {
		this.fahrStrassenId = fahrStrassenId;
		LOG.info(id + ": Fahrstrassen ID = " + fahrStrassenId);
	}

	/**
	 * Gibt das dem Signal aktuell zugeordnete Vorsignal zurueck
	 * 
	 * @return Das aktuell zugeordnete Vorsignal (<code>null</code> wenn kein Vorsignal zugeordnet ist)
	 */
	public Signal getVorsignal() {
		return vorsignal;
	}

	/**
	 * Ordnet das uebergebene Signal diesem Signal als Vorsignal zu oder loescht die Zuordnung. Wird ein Vorsignal
	 * uebergeben, wird <code>onChangeHauptsignal(this)</code> aufgerufen, um das Signalbild zwischen Haupt- und
	 * Vorsignal zu synchronisieren.
	 * 
	 * @param vorsignal Das zuzuordnende Vorsignal (<code>null</code> wenn die Zuordnung geloscht werden soll)
	 * @see #onChangeHauptsignal(Signal)
	 */
	public void setVorsignal(Signal vorsignal) {
		this.vorsignal = vorsignal;
		
		if (vorsignal != null) {
			vorsignal.onChangeHauptsignal(this);
		}
		
		LOG.info(id + ": Vorsignal = " + ((vorsignal == null) ? "-" : vorsignal.getId()));
	}

	/**
	 * Setzt das Signal (zugbewirkt) auf Halt.
	 * @see #stellen(int)
	 */
	public void setHaltfall() {
		stellen(haltBegriff);
	}

	/**
	 * Setzt das Signal manuell auf Halt (nicht zugbewirkt), ausgeloest durch die Haltfall-Drehschalter im
	 * Bedienpult.<br>
	 * Zeigt das Signal aktuell keinen Haltbegriff, wird es auf Halt gestellt (zusammen mit dem <code>HAGT</code> Flag)
	 * und speichert den manuellen Haltfall, solange der Schalter umgelegt ist.
	 * @see #zeigtHaltBegriff()
	 * @see #resetHaltfallManuell()
	 * @see #stellen(int)
	 */
	public void setHaltfallManuell() {
		LOG.info(id + ": Haltfall manuell");

		if (!zeigtHaltBegriff()) {
			stellen(haltBegriff + DBDSignalF.HAGT);
		}

		haltfallManuell = true;
	}

	/**
	 * Setzt die Speicherung des manuellen Haltfalls zurueck, ausgeloest durch das Zurueckdrehen der Haltfall-
	 * Drehschalter im Bedienpult.
	 * @see #setHaltfallManuell()
	 */
	public void resetHaltfallManuell() {
		haltfallManuell = false;
		LOG.info(id + ": Reset Haltfall manuell");
	}

	/**
	 * Gibt zurueck, ob der Haltfall-Drehschalter noch in der Haltfall-Stellung steht.
	 * 
	 * @return <code>true</code> - Drehschalter steht noch in der Haltfall-Stellung
	 */
	public boolean isHaltfallManuell() {
		LOG.info(id + ": isHaltfallManuell = " + haltfallManuell);
		return haltfallManuell;
	}
	
	/**
	 * Gibt zurueck, ob das Signal aktuell auf Halt steht
	 * 
	 * @return <code>true</code> - Signal zeigt Halt
	 * @see #zeigtHaltBegriff()
	 */
	public boolean isHalt() {
		return zeigtHaltBegriff();
	}
	
	/**
	 * Stellt das Signal auf den uebergebenen Signalbegriff (wenn der Haltfall-Drehschalter nicht auf manuellen
	 * Haltfall steht und der uergebene Signalbegriff sich vom aktuellen unterscheidet). Die Aenderung wird
	 * auch in der dem Signal zugeordneten DBD-Variablen gesetzt (Signal-ID plus <code>S</code>).<br>
	 * Wenn dem Signal aktuell ein Vorsignal zugeordnet ist, wird <code>onChangeHauptsignal(this)</code>
	 * aufgerufen, um das Signalbild zwischen Haupt- und Vorsignal zu synchronisieren.<br>
	 * Zeigt das Signal nach der Umstellung einen Haltbegriff, wird eine zugeordnete Fahrstrassen-ID geloescht.<br>
	 * Ist dem Signal ein Signalhaltmelder auf dem TCO zugeordnet, wird der Haltmelder angeschaltet, wenn das Signal
	 * einen Haltbegriff zeigt, ansonsten wird er geloescht.
	 * 
	 * @param signalBegriff Der neue Signalbegriff, auf den das Signal gestellt werden soll
	 * @see #setSignalBegriff(int)
	 * @see #onChangeHauptsignal(Signal)
	 * @see #setFahrStrassenId(String)
	 * @see #zeigtHaltBegriff()
	 */
	public void stellen(int signalBegriff) {
		if (!haltfallManuell && (signalBegriff != getSignalBegriff())) {
			main.getDbd().set(id + "S", signalBegriff);
			setSignalBegriff(signalBegriff);
			
			if (vorsignal != null) {
				vorsignal.onChangeHauptsignal(this);
			}

			if (zeigtHaltBegriff()) {
				setFahrStrassenId(null);
			}
			
			if (signalMelder[SIGNAL] != null) {
				main.getDbd().set(signalMelder[SIGNAL], zeigtHaltBegriff() ? BIT0 : 0);
			}
		}
	}
	
	/**
	 * Leere Implementierung, die nur eine Log-Ausgabe erzeugt
	 * 
	 * @param hauptSignal Das Hauptsignal, dessen Signalbild sich geaendert hat
	 */
	public void onChangeHauptsignal(Signal hauptSignal) {
		LOG.info(hauptSignal.getId() + ": Hauptsignalbegriff = " + new DBDSignalF(hauptSignal.getSignalBegriff()).toString());	
	}
	
	/**
	 * Ausgabe des Zustands des Signals
	 */
	public String toString() {
		return id + ": SB[" + new DBDSignalF(signalBegriff).toString() + "] HM[" + haltfallManuell + "]";
	}
	
	/**
	 * Gibt an, ob das Signal aktuell den Haltbegriff zeigt, der bei der Initialisierung des Signals im Konstruktur
	 * uebergeben wurde (nur den Begriff oder auch zusammen mit dem <code>HAGT</code>-Flag)
	 * 
	 * @return <code>true</code> - das Signal zeigt den im Konstruktur uebergebenen Haltbegriff
	 */
	private boolean zeigtHaltBegriff() {
		boolean zeigtHaltBegriff = (signalBegriff == haltBegriff)
								|| ((signalBegriff - DBDSignalF.HAGT) == haltBegriff);
		LOG.debug(id + ": Signalbegriff = " + new DBDSignalF(signalBegriff).toString() + ", Haltbegriff = " + new DBDSignalF(haltBegriff).toString());
		LOG.info(id + ": zeigt Haltbegriff = " + zeigtHaltBegriff);
		return zeigtHaltBegriff;
	}
}
